package com.viknix.threadpool.manager.client.core;

import com.viknix.threadpool.manager.client.constant.Constants;
import com.viknix.threadpool.manager.client.constant.PropertyKeyConst;
import com.viknix.threadpool.manager.client.exception.TpmProcessException;
import com.viknix.threadpool.manager.client.filter.ConfigFilterChainManager;
import com.viknix.threadpool.manager.client.filter.ConfigRequest;
import com.viknix.threadpool.manager.client.filter.ConfigResponse;
import com.viknix.threadpool.manager.client.http.HttpAgent;
import com.viknix.threadpool.manager.client.http.MetricsHttpAgent;
import com.viknix.threadpool.manager.client.http.ServerHttpAgent;
import com.viknix.threadpool.manager.client.http.HttpSimpleClient.HttpResult;
import com.viknix.threadpool.manager.client.listener.Listener;
import com.viknix.threadpool.manager.client.util.ContentUtils;
import com.viknix.threadpool.manager.client.util.ParamUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

/**
 * @Author: Dongqi
 * @Date: 2021/10/21 13:32
 * @Version 1.0
 * @Description 线程池配置服务（单例）
 */
@Slf4j
@SuppressWarnings("PMD.ServiceOrDaoClassShouldEndWithImplRule")
public class TpmConfigService implements ConfigService {

    private static final long POST_TIMEOUT = 3000L;

    private static final String EMPTY = "";

    /**
     * http agent
     */
    private HttpAgent agent;
    /**
     * longpolling
     */
    private ClientWorker worker;
    private String namespace;
    private String encode;
    private ConfigFilterChainManager configFilterChainManager = new ConfigFilterChainManager();

    public TpmConfigService(Properties properties, HttpAgent agent) throws TpmProcessException {
        String encodeTmp = properties.getProperty(PropertyKeyConst.ENCODE);
        if (StringUtils.isBlank(encodeTmp)) {
            encode = Constants.ENCODE;
        } else {
            encode = encodeTmp.trim();
        }
        // 初始化namespace
        initNamespace(properties);
        this.agent = new MetricsHttpAgent(agent);
        agent.start();
        worker = new ClientWorker(agent, configFilterChainManager, properties);
    }

    public TpmConfigService(Properties properties) throws TpmProcessException {
        String encodeTmp = properties.getProperty(PropertyKeyConst.ENCODE);
        if (StringUtils.isBlank(encodeTmp)) {
            encode = Constants.ENCODE;
        } else {
            encode = encodeTmp.trim();
        }
        // 初始化namespace
        initNamespace(properties);
        this.agent = new MetricsHttpAgent(new ServerHttpAgent(properties));
        agent.start();
        worker = new ClientWorker(agent, configFilterChainManager, properties);
    }

    /**
     * 从 properties 中读取 namespace
     *
     * @param properties 配置文件
     */
    private void initNamespace(Properties properties) {
        String namespaceTmp = properties.getProperty(PropertyKeyConst.NAMESPACE);
        namespace = StringUtils.isNotBlank(namespaceTmp) ? namespaceTmp.trim() : EMPTY;
        properties.put(PropertyKeyConst.NAMESPACE, namespace);
    }

    @Override
    public String getConfig(String dataId, String group, long timeoutMs) throws TpmProcessException {
        return getConfigInner(namespace, dataId, group, timeoutMs);
    }

    @Override
    public String getConfigAndSignListener(String dataId, String group, long timeoutMs, Listener listener) throws TpmProcessException {
        String content = getConfig(dataId, group, timeoutMs);
        worker.addTenantListenersWithContent(dataId, group, content, Arrays.asList(listener));
        return content;
    }

    @Override
    public void addListener(String dataId, String group, Listener listener) throws TpmProcessException {
        worker.addTenantListeners(dataId, group, Arrays.asList(listener));
    }

    @Override
    public boolean publishConfig(String dataId, String group, String content) throws TpmProcessException {
        return publishConfigInner(namespace, dataId, group, content);
    }

    @Override
    public boolean removeConfig(String dataId, String group) throws TpmProcessException {
        return removeConfigInner(namespace, dataId, group);
    }

    @Override
    public void removeListener(String dataId, String group, Listener listener) {
        worker.removeTenantListener(dataId, group, listener);
    }

    /**
     * 从服务端获取配置信息
     *
     * @param namespace 命名空间
     * @param poolId    线程池id
     * @param item      项目
     * @param timeoutMs 请求超时时间
     * @return 线程池配置信息
     * @throws TpmProcessException
     */
    private String getConfigInner(String namespace, String poolId, String item, long timeoutMs) throws TpmProcessException {
        item = null2defaultGroup(item);
        ParamUtils.checkKeyParam(poolId, item);
        ConfigResponse cr = new ConfigResponse();

        cr.setPoolId(poolId);
        cr.setNamespace(namespace);
        cr.setItem(item);

        String content = "";
        try {
            // 从服务端读取配置
            content = worker.getServerConfig(poolId, item, namespace, timeoutMs);
            cr.setContent(content);

            configFilterChainManager.doFilter(null, cr);
            content = cr.getContent();
            return content;
        } catch (Exception e) {
            log.warn("[{}] [get-config] get from server error, poolId={}, item={}, namespace={}, msg={}",
                    agent.getName(), poolId, item, namespace, e.toString());
            e.printStackTrace();
            throw new TpmProcessException(e.getMessage());
        }
    }

    /**
     * 如果项目信息是 null，获取默认的项目信息
     *
     * @param item 项目信息
     * @return 项目信息
     */
    private String null2defaultGroup(String item) {
        return (null == item) ? Constants.DEFAULT_GROUP : item.trim();
    }

    /**
     * 注销服务端配置信息
     *
     * @param namespace 明明空间
     * @param poolId    线程池id
     * @param item      项目
     * @return 是否注销成功
     * @throws TpmProcessException
     */
    private boolean removeConfigInner(String namespace, String poolId, String item) throws TpmProcessException {
        item = null2defaultGroup(item);
        ParamUtils.checkKeyParam(poolId, item);
        String url = Constants.CONFIG_CONTROLLER_PATH;
        List<String> params = new ArrayList<String>();
        params.add("poolId");
        params.add(poolId);
        params.add("item");
        params.add(item);
        if (StringUtils.isNotEmpty(namespace)) {
            params.add("namespace");
            params.add(namespace);
        }
        HttpResult result = null;
        try {
            // /v1/cs/configs
            result = agent.httpDelete(url, null, params, encode, POST_TIMEOUT);
        } catch (IOException ioe) {
            log.warn("[remove] error, " + poolId + ", " + item + ", " + namespace + ", msg: " + ioe.toString());
            return false;
        }

        if (HttpURLConnection.HTTP_OK == result.code) {
            log.info("[{}] [remove] ok, poolId={}, item={}, namespace={}", agent.getName(), poolId, item, namespace);
            return true;
        } else if (HttpURLConnection.HTTP_FORBIDDEN == result.code) {
            log.warn("[{}] [remove] error, poolId={}, item={}, namespace={}, code={}, msg={}", agent.getName(), poolId,
                    item, namespace, result.code, result.content);
            throw new TpmProcessException(result.code, result.content);
        } else {
            log.warn("[{}] [remove] error, poolId={}, item={}, namespace={}, code={}, msg={}", agent.getName(), poolId,
                    item, namespace, result.code, result.content);
            return false;
        }
    }

    /**
     * 发布服务端信息
     *
     * @param namespace 命名空间
     * @param poolId    线程池id
     * @param item      项目
     * @param content   线程池配置
     * @return 是否发布成功
     * @throws TpmProcessException
     */
    private boolean publishConfigInner(String namespace, String poolId, String item, String content) throws TpmProcessException {
        item = null2defaultGroup(item);
        ParamUtils.checkParam(poolId, item, content);

        ConfigRequest cr = new ConfigRequest();
        cr.setPoolId(poolId);
        cr.setNamespace(namespace);
        cr.setItem(item);
        cr.setContent(content);
        configFilterChainManager.doFilter(cr, null);
        content = cr.getContent();

        // /v1/cs/configs
        String url = Constants.CONFIG_CONTROLLER_PATH;
        List<String> params = new ArrayList<String>();
        params.add("poolId");
        params.add(poolId);
        params.add("item");
        params.add(item);
        params.add("content");
        params.add(content);
        if (StringUtils.isNotEmpty(namespace)) {
            params.add("namespace");
            params.add(namespace);
        }

        HttpResult result = null;
        try {
            result = agent.httpPost(url, null, params, encode, POST_TIMEOUT);
        } catch (IOException ioe) {
            log.warn("[{}] [publish-single] exception, poolId={}, item={}, msg={}", agent.getName(), poolId,
                    item, ioe.toString());
            return false;
        }
        if (HttpURLConnection.HTTP_OK == result.code) {
            log.info("[{}] [publish-single] ok, poolId={}, item={}, namespace={}, config={}", agent.getName(), poolId,
                    item, namespace, ContentUtils.truncateContent(content));
            return true;
        } else if (HttpURLConnection.HTTP_FORBIDDEN == result.code) {
            log.warn("[{}] [publish-single] error, poolId={}, item={}, namespace={}, code={}, msg={}", agent.getName(),
                    poolId, item, namespace, result.code, result.content);
            throw new TpmProcessException(result.code, result.content);
        } else {
            log.warn("[{}] [publish-single] error, poolId={}, item={}, namespace={}, code={}, msg={}", agent.getName(),
                    poolId, item, namespace, result.code, result.content);
            return false;
        }

    }

    @Override
    public String getServerStatus() {
        if (worker.isHealthServer()) {
            return "UP";
        } else {
            return "DOWN";
        }
    }
}
